/*
 * @Descripttion: gpio simulation i2c
 * @Author:     
 * @Date: 2021-12-08 14:24:32
 * @LastEditTime: 2021-12-15 10:21:18
 * 
 * reference url: https://bbs.huaweicloud.com/forum/forum/thread-57842-1-1.html
 * 
 * Copyright © 2021     , All Rights Reserved
 */

/**
    ************************************************************
    ************************************************************
    ************************************************************
    *    文件名：     i2c.c
    *
    *    作者：         张继瑞
    *
    *    日期：         2016-11-23
    *
    *    版本：         V1.0
    *
    *    说明：         软件IIC总线IO初始化，读写控制
    *
    *    修改记录：    
    ************************************************************
    ************************************************************
    ************************************************************
**/
#include "gd32f30x.h"
#include "i2c.h"
#include "log.h"
#include "delay.h"

#define SDA_H    gpio_bit_set(IIC_GPIO_PERIPH, SDAT_PIN)
#define SDA_L    gpio_bit_reset(IIC_GPIO_PERIPH, SDAT_PIN)
#define SDA_R    gpio_input_bit_get(IIC_GPIO_PERIPH, SDAT_PIN)

#define SCL_H    gpio_bit_set(IIC_GPIO_PERIPH, SCLK_PIN)
#define SCL_L    gpio_bit_reset(IIC_GPIO_PERIPH, SCLK_PIN)

#define DelayUs(n) delay_us(n)

typedef struct{
    unsigned short speed;
} IIC_INFO;

static IIC_INFO iic_info;

/*
************************************************************
*    函数名称：    IIC_SpeedCtl
*
*    函数功能：    软件IIC速度控制
*
*    入口参数：    speed：延时参数
*
*    返回参数：    无
*
*    说明：        单位：微秒
************************************************************
*/
void IIC_SpeedCtl(unsigned short speed)
{
    iic_info.speed = speed;
}

/*
************************************************************
*    函数名称：    IIC_Init
*
*    函数功能：    软件IIC总线IO初始化
*
*    入口参数：    无
*
*    返回参数：    无
*
*    说明：        使用开漏方式，这样可以不用切换IO口的输入输出方向
************************************************************
*/
//make sda adn scl mode out od
void IIC_Init(void)
{
    rcu_periph_clock_enable(IIC_RCU_GPIO);
    
    //开漏，这样不用去切换输出输入方向
    gpio_init(IIC_GPIO_PERIPH, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, SCLK_PIN | SDAT_PIN);
    gpio_bit_set(IIC_GPIO_PERIPH, SCLK_PIN | SDAT_PIN);
    
    IIC_SpeedCtl(5);
    
    SDA_H;            //拉高SDA线，处于空闲状态
    SCL_H;            //拉高SCL线，处于空闲状态
}

/*
************************************************************
*    函数名称：    IIC_Start
*
*    函数功能：    软件IIC开始信号
*
*    入口参数：    无
*
*    返回参数：    无
*
*    说明：        
************************************************************
*/
void IIC_Start(void)
{
    SDA_H;                        //拉高SDA线
    SCL_H;                        //拉高SCL线
    DelayUs(iic_info.speed);    //延时，速度控制
    
    SDA_L;                        //当SCL线为高时，SDA线一个下降沿代表开始信号
    DelayUs(iic_info.speed);    //延时，速度控制
    SCL_L;                        //钳住SCL线，以便发送数据
}

/*
************************************************************
*    函数名称：    IIC_Stop
*
*    函数功能：    软件IIC停止信号
*
*    入口参数：    无
*
*    返回参数：    无
*
*    说明：        
************************************************************
*/
void IIC_Stop(void)
{
    SDA_L;                        //拉低SDA线
    SCL_L;                        //拉低SCL先
    DelayUs(iic_info.speed);    //延时，速度控制
    
    SCL_H;                        //拉高SCL线
    SDA_H;                        //拉高SDA线，当SCL线为高时，SDA线一个上升沿代表停止信号
    DelayUs(iic_info.speed);
}

/*
************************************************************
*    函数名称：    IIC_WaitAck
*
*    函数功能：    软件IIC等待应答
*
*    入口参数：    timeOut：超时时间
*
*    返回参数：    无
*
*    说明：        单位：微秒
************************************************************
*/
_Bool IIC_WaitAck(unsigned int timeOut)
{
    SDA_H;DelayUs(iic_info.speed);            //拉高SDA线
    SCL_H;DelayUs(iic_info.speed);            //拉高SCL线
    
    while(SDA_R)                            //如果读到SDA线为1，则等待。应答信号应是0
    {
        if(--timeOut == 0)
        {
            LOGD("WaitAck TimeOut\r\n");

            IIC_Stop();                        //超时未收到应答，则停止总线
            
            return IIC_Err;                    //返回失败
        }
        
        DelayUs(iic_info.speed);
    }
    
    SCL_L;                                    //拉低SCL线，以便继续收发数据
    
    return IIC_OK;                            //返回成功
}

/*
************************************************************
*    函数名称：    IIC_Ack
*
*    函数功能：    软件IIC产生一个应答
*
*    入口参数：    无
*
*    返回参数：    无
*
*    说明：        当SDA线为低时，SCL线一个正脉冲代表发送一个应答信号
************************************************************
*/
void IIC_Ack(void)
{
    SCL_L;                        //拉低SCL线
    SDA_L;                        //拉低SDA线
    DelayUs(iic_info.speed);
    SCL_H;                        //拉高SCL线
    DelayUs(iic_info.speed);
    SCL_L;                        //拉低SCL线
}

/*
************************************************************
*    函数名称：    IIC_NAck
*
*    函数功能：    软件IIC产生一非个应答
*
*    入口参数：    无
*
*    返回参数：    无
*
*    说明：        当SDA线为高时，SCL线一个正脉冲代表发送一个非应答信号
************************************************************
*/
void IIC_NAck(void)
{
    SCL_L;                        //拉低SCL线
    SDA_H;                        //拉高SDA线
    DelayUs(iic_info.speed);
    SCL_H;                        //拉高SCL线
    DelayUs(iic_info.speed);
    SCL_L;                        //拉低SCL线
}

/*
************************************************************
*    函数名称：    IIC_SendByte
*
*    函数功能：    软件IIC发送一个字节
*
*    入口参数：    byte：需要发送的字节
*
*    返回参数：    无
*
*    说明：        
************************************************************
*/
void IIC_SendByte(unsigned char byte)
{
    unsigned char count = 0;
    
    SCL_L;                            //拉低时钟开始数据传输
    
    for(; count < 8; count++)        //循环8次，每次发送一个bit
    {
        if(byte & 0x80)                //发送最高位
            SDA_H;
        else
            SDA_L;
        
        byte <<= 1;                    //byte左移1位
        
        DelayUs(iic_info.speed);
        SCL_H;
        DelayUs(iic_info.speed);
        SCL_L;
    }
}

/*
************************************************************
*    函数名称：    IIC_RecvByte
*
*    函数功能：    软件IIC接收一个字节
*
*    入口参数：    无
*
*    返回参数：    接收到的字节数据
*
*    说明：        
************************************************************
*/
unsigned char IIC_RecvByte(void)
{
    unsigned char count = 0, receive = 0;
    
    SDA_H;                            //拉高SDA线，开漏状态下，需线拉高以便读取数据
    
    for(; count < 8; count++ )        //循环8次，每次发送一个bit
    {
        SCL_L;
        DelayUs(iic_info.speed);
        SCL_H;
        
        receive <<= 1;                //左移一位
        
        if(SDA_R)                    //如果SDA线为1，则receive变量自增，每次自增都是对bit0的+1，然后下一次循环会先左移一次
            receive++;
        
        DelayUs(iic_info.speed);
    }
    
    return receive;
}

/*
************************************************************
*    函数名称：    I2C_WriteByte
*
*    函数功能：    软件IIC写一个数据
*
*    入口参数：    slaveAddr：从机地址
*                regAddr：寄存器地址
*                byte：需要写入的数据
*
*    返回参数：    0-写入成功    1-写入失败
*
*    说明：        *byte是缓存写入数据的变量的地址，因为有些寄存器只需要控制下寄存器，并不需要写入值
************************************************************
*/
_Bool I2C_WriteByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *byte)
{
    unsigned char addr = 0;

    addr = slaveAddr << 1;        //IIC地址是7bit，这里需要左移1位，bit0：1-读    0-写
    
    IIC_Start();                //起始信号
    
    IIC_SendByte(addr);            //发送设备地址(写)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_SendByte(regAddr);        //发送寄存器地址
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    if(byte)
    {
        IIC_SendByte(*byte);    //发送数据
        if(IIC_WaitAck(5000))    //等待应答
            return IIC_Err;
    }
    
    IIC_Stop();                    //停止信号
    
    return IIC_OK;
}

/*
************************************************************
*    函数名称：    I2C_ReadByte
*
*    函数功能：    软件IIC读取一个字节
*
*    入口参数：    slaveAddr：从机地址
*                regAddr：寄存器地址
*                val：需要读取的数据缓存
*
*    返回参数：    0-成功        1-失败
*
*    说明：        val是一个缓存变量的地址
************************************************************
*/
_Bool I2C_ReadByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *val)
{

    unsigned char addr = 0;

    addr = slaveAddr << 1;        //IIC地址是7bit，这里需要左移1位，bit0：1-读    0-写
    
    IIC_Start();                //起始信号
    
    IIC_SendByte(addr);            //发送设备地址(写)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_SendByte(regAddr);        //发送寄存器地址
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_Start();                //重启信号
    
    IIC_SendByte(addr + 1);        //发送设备地址(读)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    *val = IIC_RecvByte();        //接收
    IIC_NAck();                    //产生一个非应答信号，代表读取接收
    
    IIC_Stop();                    //停止信号
    
    return IIC_OK;
}

/*
************************************************************
*    函数名称：    I2C_WriteBytes
*
*    函数功能：    软件IIC写多个数据
*
*    入口参数：    slaveAddr：从机地址
*                regAddr：寄存器地址
*                buf：需要写入的数据缓存
*                num：数据长度
*
*    返回参数：    0-写入成功    1-写入失败
*
*    说明：        *buf是一个数组或指向一个缓存区的指针
************************************************************
*/
_Bool I2C_WriteBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num)
{
    unsigned char addr = 0;

    addr = slaveAddr << 1;        //IIC地址是7bit，这里需要左移1位，bit0：1-读    0-写
    
    IIC_Start();                //起始信号
    
    IIC_SendByte(addr);            //发送设备地址(写)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_SendByte(regAddr);        //发送寄存器地址
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    while(num--)                //循环写入数据
    {
        IIC_SendByte(*buf);        //发送数据
        if(IIC_WaitAck(5000))    //等待应答
            return IIC_Err;
        
        buf++;                    //数据指针偏移到下一个
        
        DelayUs(10);
    }
    
    IIC_Stop();                    //停止信号
    
    return IIC_OK;
}

/*
************************************************************
*    函数名称：    I2C_ReadBytes
*
*    函数功能：    软件IIC读多个数据
*
*    入口参数：    slaveAddr：从机地址
*                regAddr：寄存器地址
*                buf：需要读取的数据缓存
*                num：数据长度
*
*    返回参数：    0-写入成功    1-写入失败
*
*    说明：        *buf是一个数组或指向一个缓存区的指针
************************************************************
*/
_Bool I2C_ReadBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num)
{
    unsigned short addr = 0;

    addr = slaveAddr << 1;        //IIC地址是7bit，这里需要左移1位，bit0：1-读    0-写
    
    IIC_Start();                //起始信号
    
    IIC_SendByte(addr);            //发送设备地址(写)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_SendByte(regAddr);        //发送寄存器地址
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    IIC_Start();                //重启信号
    
    IIC_SendByte(addr + 1);        //发送设备地址(读)
    if(IIC_WaitAck(5000))        //等待应答
        return IIC_Err;
    
    while(num--)
    {
        *buf = IIC_RecvByte();
        buf++;                    //偏移到下一个数据存储地址
        
        if(num == 0)
        {
           IIC_NAck();            //最后一个数据需要回NOACK
        }
        else
        {
          IIC_Ack();            //回应ACK
        }
    }
    
    IIC_Stop();
    
    return IIC_OK;
}
